﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {

    public class AnimationTrack : Object3D {

        #region Constant
        // Target Property (Fixed function)
        public const int Alpha             = 256;
        public const int AmbientColor      = 257;
        public const int Color             = 258;
        public const int Crop              = 259;
        public const int Density           = 260;
        public const int DiffuseColor      = 261;
        public const int EmissiveColor     = 262;
        public const int FarDistance       = 263;
        public const int FieldOfView       = 264;
        public const int Intensity         = 265;
        public const int MorphWeights      = 266;
        public const int NearDistance      = 267;
        public const int Orientation       = 268;
        public const int Pickability       = 269;
        public const int Scale             = 270;
        public const int Shininess         = 271;
        public const int SpecularColor     = 272;
        public const int SpotAngle         = 273;
        public const int SpotExponent      = 274;
        public const int Translation       = 275;
        public const int Visibility        = 276;
        public const int AnimationPosition = 277;
        public const int AnimationSpeed    = 278;
        public const int AnimationWeight   = 279;
        public const int BoundingBox       = 280;
        public const int BoundingSphere    = 281;
        public const int CollisitonShape   = 282;
        public const int Depth             = 283;
        public const int PointSize         = 284;
        public const int Stencil           = 285;

        #endregion

        #region Field
        AnimationController controller;
        KeyframeSequence    keyframeSequence;
        int                 targetProperty;    // ターゲット又はシェーダー変数のタイプ
        bool                normalizeEnabled;
        #endregion

        // ターゲットは従来通りのシーンノードのプロパティ
        #region Method
        public AnimationTrack (KeyframeSequence sequence, int property) {
            if (sequence == null) {
                throw new ArgumentNullException ("KeyframeSequence is null");
            }
            if (property < Alpha || property > Stencil) {
                throw new ArgumentException ("Property is invalid, valid range is witdhin [256, 285], prop=" + property);
            }
            if (ValidateProprtyComponentCount (property, sequence.ComponentCount) == false) {
                throw new ArgumentException ("ComponentCount of KeyframeSequence is not match to Target property");
            }

            this.controller = null;
            this.keyframeSequence = sequence;
            this.targetProperty = property;
            this.normalizeEnabled = false;
        }

        // シェーダーのUniform変数をアニメートするトラックを作るコンストラクタ
        public AnimationTrack (KeyframeSequence sequence, ShaderVariableType basicType, bool normalize) {
            if (sequence == null) {
                throw new ArgumentNullException ("Sequence is null");
            }
            if (basicType < ShaderVariableType.Float || basicType > ShaderVariableType.SamplerCube) {
                throw new ArgumentException ("BasicType must be one of symbolic constants defined in ShaderVariable, type=" + basicType);
            }
            if (basicType >= ShaderVariableType.Mat2 && basicType <= ShaderVariableType.SamplerCube) {
                throw new ArgumentException ("BasicType must not be Mat2,Mat3,Mat3x4,Mat4,Sampler2D,SamplerCube, type=" + basicType);
            }
            if (ValidateBasicTypeComponentCount (basicType, sequence.ComponentCount) == false) {
                throw new ArgumentException ("Sequence is not compatible with specified type");
            }

            this.controller = null;
            this.keyframeSequence = sequence;
            this.targetProperty = (int)basicType;
            this.normalizeEnabled = normalize;
        }

        protected AnimationTrack (AnimationTrack animationTrack) :
            base (animationTrack) {
            this.controller = animationTrack.controller;
            this.keyframeSequence = animationTrack.keyframeSequence;
            this.targetProperty = animationTrack.targetProperty;
            this.normalizeEnabled = animationTrack.normalizeEnabled;
        }



        public bool ValidateProprtyComponentCount (int property, int componentCount) {
            switch (property) {
                case Alpha: return (componentCount == 1) ? true : false;
                case AmbientColor: return (componentCount == 3) ? true : false;
                case Color: return (componentCount == 3) ? true : false;
                case Crop: return (componentCount == 2 || componentCount == 4) ? true : false;
                case Density: return (componentCount == 1) ? true : false;
                case DiffuseColor: return (componentCount == 3) ? true : false;
                case EmissiveColor: return (componentCount == 3) ? true : false;
                case FarDistance: return (componentCount == 1) ? true : false;
                case FieldOfView: return (componentCount == 1) ? true : false;
                case Intensity: return (componentCount == 1) ? true : false;
                case MorphWeights: return true;                                    // ここではチェックできない
                case NearDistance: return (componentCount == 1) ? true : false;
                case Orientation: return (componentCount == 4) ? true : false;
                case Pickability: return (componentCount == 1) ? true : false;
                case Scale: return (componentCount == 1) ? true : false;
                case Shininess: return (componentCount == 1) ? true : false;
                case SpecularColor: return (componentCount == 3) ? true : false;
                case SpotAngle: return (componentCount == 1) ? true : false;
                case SpotExponent: return (componentCount == 1) ? true : false;
                case Translation: return (componentCount == 3) ? true : false;
                case Visibility: return (componentCount == 1) ? true : false;
                case AnimationPosition: return (componentCount == 1 || componentCount == 2) ? true : false;
                case AnimationSpeed: return (componentCount == 1 || componentCount == 2) ? true : false;
                case AnimationWeight: return (componentCount == 1) ? true : false;
                case BoundingBox: return (componentCount == 6) ? true : false;
                case BoundingSphere: return (componentCount == 4) ? true : false;
                case CollisitonShape: return true;                                 // ここではチェックできない
                case Depth: return (componentCount == 1) ? true : false;
                case PointSize: return (componentCount == 1) ? true : false;
                case Stencil: return (componentCount == 1) ? true : false;
                default: return false;
            }
        }

        /**
         * 配列をターゲットする場合は配列の長さ分チャンネルが必要だが、
         * この段階ではまだチェックできない
         * */
        public bool ValidateBasicTypeComponentCount (ShaderVariableType basicType, int componentCount) {
            switch (basicType) {
                case ShaderVariableType.Float: return (componentCount == 1) ? true : false;
                case ShaderVariableType.Bool: return (componentCount == 1) ? true : false;
                case ShaderVariableType.Int: return (componentCount == 1) ? true : false;
                case ShaderVariableType.Vec2: return (componentCount == 2) ? true : false;
                case ShaderVariableType.Vec3: return (componentCount == 3) ? true : false;
                case ShaderVariableType.Vec4: return (componentCount == 4) ? true : false;
                case ShaderVariableType.BVec2: return (componentCount == 2) ? true : false;
                case ShaderVariableType.BVec3: return (componentCount == 3) ? true : false;
                case ShaderVariableType.BVec4: return (componentCount == 4) ? true : false;
                case ShaderVariableType.IVec2: return (componentCount == 2) ? true : false;
                case ShaderVariableType.IVec3: return (componentCount == 3) ? true : false;
                case ShaderVariableType.IVec4: return (componentCount == 4) ? true : false;
                default: return false;
            }
        }

        public AnimationController Controller {
            get { return controller; }
        }

        /**
         * 俺のメモ: コントローラーをセットしたら必ず「コントローラーの方にも」このアニメーショントラックをセットしなければならない。
         * 向こうでDurationを計算するのに影響のあるトラックのセットが必要。
         * これ下手すると循環しかねないが...
         * 循環したのでコメントアウト。
         * Durationは後で考える。
         * */
        public void SetController (AnimationController controller) {

            //if (this.controller != null) {
            //    this.controller.RemoveAnimationTrack (this);
            //}
            //if (controller != null) {
            //    controller.AddAnimationTrack (this);
            //}

            this.controller = controller;
        }

        public KeyframeSequence KeyframeSequence {
            get { return keyframeSequence; }
        }

        public void SetKeyframeSequence (KeyframeSequence sequence) {
            if (sequence == null) {
                throw new ArgumentNullException ("Sequence is null");
            }
            if (sequence.ComponentCount != this.keyframeSequence.ComponentCount ||
                sequence.ChanelCount != this.keyframeSequence.ChanelCount) {
                throw new ArgumentException ("Replacing sequence must has same component count and channel count with replaced sequence");
            }

            this.keyframeSequence = sequence;
        }


        public int TargetProperty {
            get { return targetProperty; }
        }

        public bool NormalizeEnabled {
            get {
                if (targetProperty >= Alpha && targetProperty <= Stencil) {
                    throw new InvalidOperationException ("Targeting propety is not ShaderUniforms, property=" + targetProperty);
                }

                return normalizeEnabled;
            }
        }

        public float Sample (int worldTime, int channel) {
            if (keyframeSequence.ComponentCount != 1) {
                throw new InvalidOperationException ("Component count must be 1, count=" + keyframeSequence.ComponentCount);
            }
            if (controller == null || controller.Active (worldTime) == false) {
                throw new InvalidOperationException ("Animation controller doesn't exist or inactive in such worldTime");
            }
            if (channel < 0 || channel > keyframeSequence.ChanelCount) {
                throw new ArgumentOutOfRangeException ("Channel count is out of range [0," + keyframeSequence.ChanelCount + "]");
            }

            var sequenceTime = controller.Position (worldTime);
            return keyframeSequence.Sample (sequenceTime, channel);
        }

        public void Sample (int worldTime, int charnnel, float[] value) {
            if (value == null) {
                throw new ArgumentNullException ("Value is null");
            }
            if (value.Length < keyframeSequence.ComponentCount) {
                throw new ArgumentException ("Length of value is less than required, len=" + value.Length + ",req=" + keyframeSequence.ComponentCount + "]");
            }
        }

        public override Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (base.References);

                if (controller != null) {
                    q.Add (controller);
                    q.AddRange (controller.References);
                }
                if (keyframeSequence != null) {
                    q.Add (keyframeSequence);
                    q.AddRange (keyframeSequence.References);
                }
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public override Object3D Duplicate () {
            return new AnimationTrack(this);
        }
        #endregion
    }
}
